from datetime import datetime as DateTime
from typing import Union

from dateutil.relativedelta import relativedelta
from django.db import transaction
from django.db.models import Q
from django.utils import timezone

import backend.cews.services.LogService as LogService
import backend.cews.services.PartsService as PartsService
import backend.cews.services.StockItemService as StockItemService
from backend.cews.models import (Parts, PartsReplacement, RepairOrder, RepairOrderEDoc,StockItemEDoc,
                                 RepairOrderInspection, RepairOrderPart,
                                 RepairOrderPartReplacement,
                                 RepairOrderRepairDetails, StockItem,
                                 StockItemReplacement, Log)
from backend.cews.services.EDocService import create_repair_order_edocs
from backend.cews.services.ImpWorkService import create_repair_order_imp_works
from backend.cews.services.TaskService import create_task_for_repair_order_part

import json
import logging

logger = logging.getLogger(__name__)


def create_repair_order(
    stock_code,
    serial_no,
    work_order_no,
    mtce_type,
    report_date,
    location,
    # location_type,
    department,
    failure_symptom,
    remark,
    status,
    parent_work_order_no,
    eqpt_description,
    check_in_date,
    equipment,
    line='',
    line_work_order='',
    delay_i='',
    delay_a='',
    io_counter='',
    workgroup='',
    contractor_liability='',
    dlc_form_no='',
    designated_person_name='',
    date='',
    po_no='',
    inspection_no='',
    received_date='',
    user=''
):
    order: "RepairOrder | None" = None
    order_part: "RepairOrderPart | None" = None
    order_inspection: "RepairOrderInspection | None" = None
    # parent_order: "RepairOrder | None" = None

    if RepairOrderPart.objects.filter(
        serial_no=serial_no,
        stock_code=stock_code,
        deleted=False,
        last_check_out__isnull=True
    ).exists():
        raise ValueError("This serial No. had been checked  in, please  check it out first")

    part = Parts.objects.get(
        serial_no=serial_no,
        stock_code=stock_code,
        deleted=False
    )

    with transaction.atomic():
        # create order
        # now = DateTime.now()
        order = RepairOrder()
        order.report_date = report_date
        order.mtce_type = mtce_type
        order.work_order_no = work_order_no
        order.parent_work_order_no = parent_work_order_no
        order.department = department
        # order.location_type = location_type
        if location:
            order.car = location
        else:
            order.car = part.car

        order.location = location
        order.failure_symptom = failure_symptom ## FIXME: ? field not exisit
        order.remark = remark

        order.check_in = check_in_date
        # order.check_out

        order.workgroup = workgroup

        # FIXME: check if system is correct
        order.system = part.system
        order.deliver_to = part.deliver_to

        # FIXME: investicate later 
        order.io_counter = io_counter

        # FIXME: check if car description is correct
        order.car_description = ''

        # add new fields
        order.eqpt_description = eqpt_description
        order.equipment = equipment
        order.line = line
        order.line_work_order = line_work_order
        order.delay_i = delay_i
        order.delay_a = delay_a

        #add IGI form
        order.po_no = po_no
        order.inspection_no = inspection_no
        order.received_date = received_date

        #add created by 
        order.created_by = user
        order.save()

        payload = {
            'stock_code':stock_code,
            'serial_no':serial_no,
            'work_order_no':work_order_no,
            'mtce_type':mtce_type,
            'report_date':report_date,
            'location':location,
            'department':department,
            'failure_symptom':failure_symptom,
            'remark':remark,
            'status':status,
            'parent_work_order_no':parent_work_order_no,
            'io_counter':io_counter,
            'contractor_liability':contractor_liability,
            'dlc_form_no':dlc_form_no,
            'designated_person_name':designated_person_name,
            'date':date,
            'eqpt_description': eqpt_description,
            'check_in_date': check_in_date,
            'equipment': equipment,
            'line': line,
            'line_work_order': line_work_order,
            'delay_i': delay_i,
            'delay_a': delay_a,
            'po_no' : po_no,
            'inspection_no' : inspection_no,
            'received_date' : received_date,
        }

        part = Parts.objects.get(stock_code=stock_code, serial_no=serial_no, deleted=False)
        result = LogService.check_part_is_parent(part)

        new_log = Log(
                    username=user,
                    log_type=Log.LogType.MAINTENANCE,
                    category_type=Log.CategoryType.ORDER,
                    action_type=Log.ActionType.CREATE,
                    action='create_repair_order',
                    payload= json.dumps(payload),
                    description=f"create order: {order.order_no}",
                    repair_order=order.id,
                    parent_stock_code=result['parent_stock_code'],
                    parent_part=result['parent_serial_no'],
                    part=result['serial_no'],
                    stock_code=result['stock_code']
                )
        new_log2 = Log(
                    username=user,
                    log_type=Log.LogType.MAINTENANCE,
                    category_type=Log.CategoryType.PART,
                    action_type=Log.ActionType.CHECKIN,
                    action='create_repair_order',
                    payload= json.dumps(payload),
                    description=f"Part: {part.description} is checked in",
                    repair_order=order.id,
                    parent_stock_code=result['parent_stock_code'],
                    parent_part=result['parent_serial_no'],
                    part=result['serial_no'],
                    stock_code=result['stock_code']
                )
        new_log.save()
        new_log2.save()
        # create repair order part
        # using stockitem master relation create, not part master.
        # auto create child repair order part if it's parent
        # check if current is parent and has child
        order_part = create_repair_order_part(order=order, part=part, io_couter=io_counter, received_date=received_date)

        if part.is_parent:

            stock_item_children_relation = StockItem.objects.get(
                stock_code=part.stock_code,
                deleted=False
            ).children
            """ stock_item_parent = StockItem.objects.get(
                    stock_code=part.stock_code
                )
            stock_item_children = stock_item_parent.children """
            child_stock_dict = {
                    sr.sequence: sr
                    for sr in stock_item_children_relation
                }
            
            child_part_dict = {
                c.sequence: c
                for c in part.children
            }

            for stock_sequence in child_stock_dict:
                if stock_sequence in child_part_dict:
                    create_repair_order_part(
                        order,
                        part = child_part_dict[stock_sequence],
                        stock_item = child_stock_dict[stock_sequence].child,
                        parent_part=order_part,
                        io_couter=io_counter,
                        seq=stock_sequence
                        # status,
                    )
                else:
                    create_repair_order_part(
                        order,
                        part = None,
                        stock_item = child_stock_dict[stock_sequence].child,
                        parent_part=order_part,
                        io_couter=io_counter,
                        seq=stock_sequence

                        # status,
                    )

           

            """ children = part.children

            children_dict = {
                child.stock_code: child
                for child in children
            }

            stock_item_children_relation = StockItem.objects.get(
                stock_code=part.stock_code,
                deleted=False
            ).children

            for stock_item_relation in stock_item_children_relation:
                child_part = children_dict.get(stock_item_relation.child.stock_code, None)
                _ = create_repair_order_part(
                    order,
                    part = child_part,
                    stock_item = stock_item_relation.child,
                    parent_part=order_part,
                    io_couter=io_counter,
                    # status,
                ) """
                
            ## should always create part from stock item master
            # if len(children) > 0:
            #     for child in children:
            #         _ = create_repair_order_part(
            #             order,
            #             child,
            #             status,
            #             order_part
            #         )
            # else:
            #     stock_item_children_relation = StockItem.objects.get(
            #         stock_code=part.stock_code
            #     ).children

            #     for stock_item_child_relation in stock_item_children_relation:
            #         _ = create_repair_order_part(
            #             order,
            #             stock_item_child_relation.child,
            #             status,
            #             order_part
            #         )

        order_inspection = create_repair_order_inspection(
            order,
            contractor_liability,
            dlc_form_no,
            date, # not use
            designated_person_name # not use
        )

        # update part checkin, and reset part checkout
        part.last_check_in = check_in_date
        part.last_check_out = None
        part.last_status = status
        part.car = ''

        part.save()

        ## create repair order edoc
        create_repair_order_edocs(order)

        ## create repair order improvement work
        create_repair_order_imp_works(order)

        ## create repair order detail
        create_repair_details(order)
        
    return order, order_part, order_inspection


def check_create_parts_order_exists(stock_code, serial_no):
    if RepairOrderPart.objects.filter(
        stock_code=stock_code,
        serial_no=serial_no,
        deleted=False,
        last_check_out__isnull=True
    ).exists():
        return "This serial No. had been checked  in, please  check it out first"

def create_repair_order_part(
    order: Union[RepairOrder, int, str],
    part: Union[Parts, int, str]=None,
    stock_item:StockItem = None,
    status=RepairOrder.STATUS.CHECKED_IN,
    parent_part: Union[RepairOrderPart, int, str, None] = None,
    io_couter="",
    received_date="",
    seq=int
):
    order_part: "RepairOrderPart | None" = None


    # check order is type of RepairOrder else query it
    if not isinstance(order, RepairOrder):
        order = RepairOrder.objects.get(id=order, deleted=False)

    # check part is type of Parts or StockItem else query it
    if not isinstance(part, Parts):
        try:
            part = Parts.objects.get(id=part, deleted=False)
        except Parts.DoesNotExist:
            print("part not found, id:"+str(part))


    if not isinstance(stock_item, StockItem):
        try:
            stock_item = StockItem.objects.get(id=stock_item, deleted=False)
        except StockItem.DoesNotExist:
            print("stock item not found, id:"+str(stock_item))


    if not part and not stock_item:
        raise ValueError("part or stock item must be provided")
    

    with transaction.atomic():
        order_part = RepairOrderPart()
        order_part.repair_order = order

        if parent_part is not None:
            order_part.parent_part = parent_part

        order_part.stock_code = ''
        order_part.outer_inner = RepairOrderPart.OIType.INNER
        order_part.system = order.system

        order_part.serial_no = ""
        order_part.sw_ver = ""
        order_part.hw_ver = ""
        order_part.sequence = seq
        order_part.status = status
        order_part.status_details = ''  # FIXME: check status details
        order_part.io_counter = ''  # default empty, since it is checkin
        order_part.detail_description = ''
        order_part.qr_code = ""
        order_part.deliver_to = ""
        order_part.fitment = RepairOrderPart.FitmentType.DEFITMENT
        order_part.parts = None

        # default in out time
        order_part.last_check_in = order.check_in
        order_part.last_check_out = None

        # add IGI 

        order_part.po_no =order.po_no,
        order_part.inspection_no=order.inspection_no,
        order_part.received_date=order.received_date,

        if part: ## if part, fill fields from part
            order_part.parts = part
            if part.is_outer:
                order_part.outer_inner = RepairOrderPart.OIType.OUTER
            order_part.stock_code = part.stock_code
            order_part.serial_no = part.serial_no
            order_part.sw_ver = part.sw_ver
            order_part.hw_ver = part.hw_ver
            order_part.sequence = part.sequence
            order_part.qr_code = part.qr_code
            order_part.deliver_to = part.deliver_to  # FIXME: check deliver_to
            order_part.detail_description = part.detail_description
            order_part.description = part.description
            order_part.date_code = part.date_code
            order_part.io_counter = part.io_counter ## FIXME: check io_counter
            order_part.fitment = RepairOrderPart.FitmentType.FITMENT

            ## update io counter
            part.io_counter = io_couter
            part.car = order.car
            part.location = order.location

            # part.location = io_couter
            part.save()
            
            
        if not part and stock_item: ## if stock item, fill fields from stock item
            order_part.stock_code = stock_item.stock_code
            order_part.system  = stock_item.system
            order_part.description = stock_item.description
            order_part.date_code = stock_item.date_code
            order_part.sw_ver = stock_item.sw_ver
            order_part.hw_ver = stock_item.hw_ver
            order_part.io_counter = stock_item.io_counter ## FIXME: check io_counter is nessary
            order_part.detail_description = stock_item.detail_description

        # FIXME: check is part.sequence or status.sequence
        order_part.save()

        if part:
            create_repair_order_part_replacement(
                order,
                order_part,
                part
            )
        else:
            create_repair_order_part_replacement(
                order,
                order_part,
                stock_item=stock_item
            )

        # create repair order part task
        # only create when it is parent part
        if order_part.parent_part is None:
            create_task_for_repair_order_part(order_part)

    return order_part


def create_repair_details(repair_order:Union[int,RepairOrder]):
    if not isinstance(repair_order, RepairOrder):
        repair_order = RepairOrder.objects.get(id=repair_order, deleted=False)
    repair_order:RepairOrder

    detail = RepairOrderRepairDetails()
    detail.repair_order = repair_order
    detail.delivery_to = repair_order.deliver_to
    detail.failure_symptom = repair_order.failure_symptom
    detail.actions = ''
    detail.remarks = ''
    detail.save()
    return detail


def create_repair_order_part_replacement(order, order_part, part:Parts=None,stock_item:StockItem= None):
    new_repair_order_part_replacement = None

    
    if isinstance(part, Parts):
        part_replacements = PartsReplacement.objects.filter(parts=part, deleted=False)
        for replacements in part_replacements:
            new_repair_order_part_replacement = RepairOrderPartReplacement()
            new_repair_order_part_replacement.period = replacements.period
            new_repair_order_part_replacement.repair_order = order
            new_repair_order_part_replacement.repair_order_part = order_part    
            new_repair_order_part_replacement.component = \
                replacements.component

            new_repair_order_part_replacement.last_replaced = \
                replacements.last_replaced

            new_repair_order_part_replacement.replaced = None
            with transaction.atomic():
                new_repair_order_part_replacement.save()
        
    else:
        part_replacements = StockItemReplacement.objects.filter(
            stock_item__stock_code=stock_item.stock_code,
            deleted=False
        )

        for replacements in part_replacements:
            new_repair_order_part_replacement = RepairOrderPartReplacement()
            new_repair_order_part_replacement.period = replacements.period
            new_repair_order_part_replacement.repair_order = order
            new_repair_order_part_replacement.repair_order_part = order_part    
            new_repair_order_part_replacement.component = \
                replacements.component

            new_repair_order_part_replacement.last_replaced = None
            new_repair_order_part_replacement.replaced = None
            with transaction.atomic():
                new_repair_order_part_replacement.save()

    return new_repair_order_part_replacement


def update_repair_order_part_replacement(repair_order_part:RepairOrderPart):
    '''
        Update replacement for repair order part.
        will query repair order part replacement by component name, assumed component name is unique
    '''
    c_rop_replacements = RepairOrderPartReplacement.objects.filter(
        repair_order_part=repair_order_part,
        deleted=False
    )
    
    parts_replacements = PartsReplacement.objects.filter(
        parts=repair_order_part.part,
        deleted=False
    )
    
    parts_dict = {
        part_replacement.component:part_replacement
        for part_replacement in parts_replacements
    }
    
    for rop_replacement in c_rop_replacements:
        key = rop_replacement.component
        p_replacement = parts_dict.get(key,None)
        if p_replacement:
            if p_replacement.last_replaced:
                rop_replacement.last_replaced = p_replacement.last_replaced
            else:
                rop_replacement.last_replaced = None
            rop_replacement.complete = False
            rop_replacement.replaced = None
            rop_replacement.save()


def empty_repair_order_part_replacement(repair_order_part:RepairOrderPart):
    rop_replacements = RepairOrderPartReplacement.objects.filter(
        repair_order_part=repair_order_part,
        deleted=False
    )

    for replacement in rop_replacements:
        replacement.last_replaced = None
        replacement.complete = False
        replacement.replaced = None
        replacement.save()
        

def fitment(
    repair_order_part_id,
    serial_no
):
    '''
    Fitment : add serial to repair order part.
    '''

    print(repair_order_part_id, serial_no)

    repair_order_part = RepairOrderPart.objects.get(
        id=repair_order_part_id,
        deleted=False
    )

   
    # if RepairOrderPart.objects.exclude(repair_order=repair_order_part.repair_order).filter(stock_code=repair_order_part.stock_code,serial_no=repair_order_part.serial_no, deleted=False, status="CHECKED IN").exists():
    #     pp = Parts.objects.get(repair_order_part.stock_code, repair_order_part.serial_no, deleted=False)
    #     raise ValueError(f"{pp.description}, {pp.serial_no} has already in maintenance")


    origin_serial_no = repair_order_part.serial_no
    
    parts = PartsService.get_part(
        repair_order_part.stock_code,
        serial_no,
    )

    if RepairOrderPart.objects.exclude(repair_order=repair_order_part.repair_order).filter(stock_code=repair_order_part.stock_code,serial_no=serial_no, deleted=False, status="CHECKED IN").exists():
        raise ValueError(f"{serial_no} has already in other maintenance")

    if len(parts) == 0:
        raise ValueError("Part not found, please register it first.")
    
    if RepairOrderPart.objects.filter(
        repair_order=repair_order_part.repair_order,
        stock_code=repair_order_part.stock_code,
        serial_no=serial_no,
        deleted=False
    ).exists():
        old = RepairOrderPart.objects.get(repair_order=repair_order_part.repair_order,stock_code=repair_order_part.stock_code,serial_no=serial_no,deleted=False)
        empty_repair_order_edoc(old.stock_code,old.serial_no, old.repair_order)
        old.serial_no = ""
        old.parts = None
        old.save()

    repair_order_part.serial_no = serial_no
    repair_order_part.parts = parts[0]


    with transaction.atomic():
        repair_order_part.save()
        LogService.create_repair_order_part_fitment_log(serial_no, parts[0].id)

    update_repair_order_part_replacement(repair_order_part)
    update_repair_order_edoc(parts[0], repair_order_part.repair_order, origin_serial_no)
    
    return True
    # update repair order part replacement
    # current_order_replacement = RepairOrderPartReplacement.objects.get(
    #     repair_order_part = repair_order_part 
    # )

    # try:
    #     part_replacements = PartsReplacement.objects.filter(
    #         parts__stock_code=repair_order_part.stock_code,
    #         parts__serial_no=serial_no
    #     )

    #     for part_replacement in part_replacements:
    #         current_order_replacement.component = \
    #             part_replacement.component

    #         current_order_replacement.last_replaced = \
    #             part_replacement.last_replaced

    #         current_order_replacement.replaced = None
    #         current_order_replacement.complete = False

    #         with transaction.atomic():
    #             current_order_replacement.save()

    #     return True
    # except PartsReplacement.DoesNotExist as ex:
    #     print(ex)

    # current_order_replacement.component = ""
    # current_order_replacement.last_replaced = None
    # current_order_replacement.replaced = None
    # current_order_replacement.complete = False
    # with transaction.atomic():
    #     current_order_replacement.save()
    #     return True


def defitment(
    repair_order_part_id
):
    '''
    Defitment : remove serial from repair order part.
    '''

    repair_order_part = RepairOrderPart.objects.get(
        id=repair_order_part_id,
        deleted=False
    )

    origin_stock_code = repair_order_part.stock_code
    origin_serial_no = repair_order_part.serial_no

    repair_order_part.serial_no = ""
    repair_order_part.parts = None

    with transaction.atomic():
        repair_order_part.save()
        LogService.create_repair_order_part_fitment_log("", repair_order_part_id)
        empty_repair_order_part_replacement(repair_order_part)
        empty_repair_order_edoc(origin_stock_code,origin_serial_no, repair_order_part.repair_order)
        
    # # delete repair order part replacement
    # current_order_replacement = RepairOrderPartReplacement.objects.get(
    #     repair_order_part=repair_order_part
    # )

    # current_order_replacement.component = ""
    # current_order_replacement.complete = False
    # current_order_replacement.last_replaced = None
    # current_order_replacement.replaced = None

    # with transaction.atomic():
    #     current_order_replacement.save()
    #     return True

    return False


def update_fitment(
    repair_order_part_id
):
    '''
    Update fitment : update relationship in Parts master when job complete.
    '''
    repair_order_part = RepairOrderPart.objects.get(
        id=repair_order_part_id,
        deleted=False
    )
    
    repair_order_part_children = repair_order_part.children

    for child in repair_order_part_children:
        child_part = PartsService.get_part(
            child.stock_code,
            child.serial_no
        )[0]

        child_part.parent_part = \
            repair_order_part.part

        with transaction.atomic():
            child_part.save()

   
    return True


def create_repair_order_inspection(
    order: RepairOrder,
    contractor_liability: str,
    dlc_no: str, # not use
    date: str, # not use
    designer: str
) -> "RepairOrderInspection | None":
    order_inspection: "RepairOrderInspection | None" = None

    if contractor_liability == "yes":
        contractor_liability = RepairOrderInspection.LiType.YES
    else:
        contractor_liability = RepairOrderInspection.LiType.NO

    with transaction.atomic():
        order_inspection = RepairOrderInspection(
            repair_order_id=order.id,
            contractor_liability=contractor_liability,
            dlc_no=dlc_no,
            date=date,
            designer=designer
        )

        order_inspection.save()

    return order_inspection

def update_repair_order_inspection(
    order_id: int,
    contractor_liability: str,
    dlc_no: str,
) -> "RepairOrderInspection | None":
    order_inspection: "RepairOrderInspection | None" = None

    if contractor_liability == "yes":
        contractor_liability = RepairOrderInspection.LiType.YES
    else:
        contractor_liability = RepairOrderInspection.LiType.NO

    with transaction.atomic():
        order_inspection = RepairOrderInspection.objects.get(repair_order_id=order_id)
        order_inspection.dlc_no = dlc_no
        order_inspection.contractor_liability = contractor_liability

        order_inspection.save()

    return order_inspection


def get_repair_order_inspection(repair_order_id):
    return RepairOrderInspection.objects.get(
        repair_order_id=repair_order_id,
        deleted=False
    )


def get_repair_order_parts(repair_order_id):
    return RepairOrderPart.objects.filter(
        repair_order__id=repair_order_id,
        deleted=False
    ).select_related("parts").order_by('sequence')


def get_order_parts(
    stock_code="",
    serial_no="",
    status="",
    order_id=-1,
    mtce_type="",
    short_description="",
    complete_status=False
):
    mtce_type_list = [choices[0] for choices in RepairOrder.MTCE_TYPE.choices] 

    query = Q()
    query.add(Q(deleted=False), Q.AND)
    if order_id != -1:
        query.add(Q(order_id=order_id), Q.AND)
    
    status_list = []
    if status:
        status_list.append(status)
    else:
        status_list.append(RepairOrder.STATUS.CHECKED_IN)
        # status_list.append(RepairOrder.STATUS.CHECKED_OUT)
        status_list.append(RepairOrder.STATUS.IN_PROCESS)
        status_list.append(RepairOrder.STATUS.WAITING_FOR_ENDORSEMENT)
        status_list.append(RepairOrder.STATUS.WAITING_FOR_SIC)
        status_list.append(RepairOrder.STATUS.WAITING_FOR_JOB_COMPLETED)
        
        
    if complete_status:
        status_list.append(RepairOrder.STATUS.COMPLETE)
        status_list.append(RepairOrder.STATUS.FORCE_COMPLETE)
    

    query.add(Q(status__in=status_list), Q.AND)
    
    if stock_code:
        query.add(Q(stock_code__iexact=stock_code), Q.AND)
    
    if serial_no:
        query.add(Q(serial_no__iexact=serial_no), Q.AND)

    if mtce_type in mtce_type_list:
        query.add(Q(repair_order__mtce_type__in=[mtce_type]), Q.AND)
    
    if short_description:
        query.add(Q(repair_order__eqpt_description__icontains=short_description), Q.AND)
    
    ret = []
    dict = {
        rop_r.repair_order : rop_r
        for rop_r in RepairOrderPart.objects.select_related("repair_order").order_by('-id').filter(query)
    }
    
    for repair_order in dict:
        if repair_order.status not in status_list:
            continue
        primary_part = RepairOrder.objects.get(pk=repair_order.id).primary_part
        if primary_part.status not in status_list:
            continue
        ret.append(primary_part)

    return ret

    
def get_can_checkout_parts(
    stock_code="",
    serial_no="",
):
    query = Q()
    query.add(Q(deleted=False), Q.AND)
    query.add(Q(repair_order__check_out__isnull=True), Q.AND)

    if ("*" in stock_code) and (len(stock_code) != 1): 
        query.add(Q(stock_code__icontains=stock_code.replace("*", "")), Q.AND)
    elif stock_code != "":
        query.add(Q(stock_code__iexact=stock_code), Q.AND)
    
    if ("*" in serial_no) and (len(serial_no) != 1):
        query.add(Q(serial_no__icontains=serial_no.replace("*", "")), Q.AND)
    elif serial_no != "":
        query.add(Q(serial_no__iexact=serial_no), Q.AND)

    dict = {
        rop_r.repair_order : rop_r
        for rop_r in RepairOrderPart.objects.select_related("repair_order").filter(
                query,
            ).filter(
                Q(status=RepairOrder.STATUS.COMPLETE)\
                    | Q(status=RepairOrder.STATUS.FORCE_COMPLETE)
            )
    }

    ret = []

    for repair_order in dict:

        primary_part = RepairOrder.objects.get(pk=repair_order.id).primary_part
        
        ret.append(primary_part)

    return ret
    
    # return RepairOrderPart.objects.select_related("repair_order").filter(
    #     query,
    # ).filter(
    #     Q(status=RepairOrder.STATUS.COMPLETE)\
    #         | Q(status=RepairOrder.STATUS.FORCE_COMPLETE)
    # )


def get_part_detail(stock_code):
    stock_item = StockItem.objects.get(
        stock_code=stock_code,
        deleted=False
    )

    return stock_item.children


def get_maintenance_history(
    serial_numbers=[],
    repair_order_id=-1
):
    maintenance_history = {}
    result = []
    if len(serial_numbers) == 0 and repair_order_id == -1:
        return None

    if repair_order_id != -1:
        serial_numbers = RepairOrderPart.objects.filter(
            repair_order_id=repair_order_id,
            deleted=False
        ).values_list("serial_no", flat=True).distinct()

    repair_order_parts = RepairOrderPart.objects.filter(
        serial_no__in=serial_numbers,
        deleted=False
    )

    for serial_number in serial_numbers:
        maintenance_history["serial_no"] = serial_number
        maintenance_history["maintenance_history"] = \
            repair_order_parts.filter(
                serial_no=serial_number
            ).order_by('-repair_order_id')[:5]

        result.append(maintenance_history.copy())
        maintenance_history.clear()

    return result


def create_replacement_task(
    order_id,
    order_part_id,
    component,
):
    
    new_repair_order_task = RepairOrderPartReplacement(
        repair_order=RepairOrder.objects.get(pk=order_id, deleted=False),
        repair_order_part=RepairOrderPart.objects.get(pk=order_part_id, deleted=False),
        component=component,
        replaced=None,
        order_only=True
    )

    with transaction.atomic():
        new_repair_order_task.save()
        return True

    return False


def confirm_repair_order(order_id, check):
    current_order = RepairOrder.objects.get(
        id=order_id,
        deleted=False
    )

    current_order.checked = check
    repair_order_part = current_order.primary_part

    repair_order_part.status = current_order.status = RepairOrder.STATUS.IN_PROCESS
     
    with transaction.atomic():
        current_order.save()
        repair_order_part.save()
        return True

    return False


def update_replacement_task(
    id,
    completed: bool,
    update_time
):
    current_repair_order_task = \
        RepairOrderPartReplacement.objects.get(
            id=id,
            deleted=False
        )
    
    if completed:
        update_time = update_time.replace('-','/')
        date_object = DateTime.strptime(update_time, '%Y/%m')
        formatted_date = date_object.strftime('%Y-%m-%d %H:%M:%S')
    else:
        formatted_date = None
    current_repair_order_task.complete = completed
    
    current_repair_order_task.replaced = formatted_date
    

    with transaction.atomic():
        current_repair_order_task.save()
        return True

    return False


def get_hard_time_replacement(repair_order_id):
    result = []

    if not RepairOrderPartReplacement.objects.filter(
            repair_order=repair_order_id,
            deleted=False
        ).exists():
        repair_order = RepairOrder.objects.get(pk=repair_order_id)
        rops = RepairOrderPart.objects.filter(repair_order=repair_order)
        for rop in rops:
            create_repair_order_part_replacement(repair_order,rop, rop.parts )
    
    # parent and chidren repair replacement tasks for current order.
    repair_order_replacements = \
        RepairOrderPartReplacement.objects.filter(
            repair_order=repair_order_id,
        )

    for repair_order_replacement in repair_order_replacements:
        if repair_order_replacement.defitted:
            continue

        repair_replacement_dict = {}
        repair_replacement_dict["id"] = \
            repair_order_replacement.id

        repair_replacement_dict["stock_code"] = \
            repair_order_replacement.repair_order_part.stock_code
        
        repair_replacement_dict["description"] = \
            repair_order_replacement.repair_order_part.description

        repair_replacement_dict["serial_no"] = \
            repair_order_replacement.repair_order_part.serial_no

        repair_replacement_dict["last_replaced"] = \
            repair_order_replacement.last_replaced
            
        repair_replacement_dict["complete"] = \
            repair_order_replacement.complete
        
        repair_replacement_dict["replaced"] = None
        if repair_order_replacement.replaced is not None:
            repair_replacement_dict["replaced"] = \
                timezone.localtime(repair_order_replacement.replaced)
            
        repair_replacement_dict["period"] = \
            repair_order_replacement.period

        repair_replacement_dict["next_replace"] = \
            repair_order_replacement.next_replace
        
        # determine if the replacement task is one time only.
        repair_replacement_dict["order_only"] = repair_order_replacement.order_only
        repair_replacement_dict["one_time"] = repair_order_replacement.one_time
        repair_replacement_dict["component"] = \
            repair_order_replacement.component
            
        repair_replacement_dict["diff"] = \
            repair_order_replacement.diff
        
        repair_replacement_dict["is_expired"] = \
            repair_order_replacement.is_expired
        
        repair_replacement_dict["is_critical"] = \
            repair_order_replacement.is_critical
        
        repair_replacement_dict["deleted"] = \
            repair_order_replacement.deleted
        
        result.append(repair_replacement_dict)
    return result


def delete_order_one_time_replacement_task(replacement_task_id):
    current_replacment_task = RepairOrderPartReplacement.objects.get(
        pk=replacement_task_id
    )
    
    # current_replacment_task.deleted = True
    
    with transaction.atomic():
        current_replacment_task.delete()
        return True
    
    return False
# {"CAT":"666666666","SN":"21525255"}
def update_repair_order_edoc(part, repair_order, origin_serial_no):
    sie = StockItemEDoc.objects.filter(stock_code=part.stock_code, deleted=False)
    
    for edoc in sie:
            
        roe = RepairOrderEDoc.objects.filter(
                stock_code=edoc.stock_code, repair_order=repair_order,serial_no=origin_serial_no,
                doc_name=edoc.doc_name,version=edoc.version,deleted=False).first()
        if roe:
            roe.parts = part
            roe.complete = edoc.complete
            roe.serial_no = part.serial_no
            roe.save()
        
        else:
            roe = RepairOrderEDoc.objects.filter(
                stock_code=edoc.stock_code, repair_order=repair_order,serial_no='',
                doc_name=edoc.doc_name,version=edoc.version,deleted=False).first()
            roe.parts = part
            roe.complete = edoc.complete
            roe.serial_no = part.serial_no
            roe.save()
            



def empty_repair_order_edoc(origin_stock_code,origin_serial_no, repair_order):
    sie = StockItemEDoc.objects.filter(stock_code=origin_stock_code, deleted=False)
    for edoc in sie:
        roe = RepairOrderEDoc.objects.filter(
            stock_code=edoc.stock_code,serial_no=origin_serial_no, repair_order=repair_order,
            doc_name=edoc.doc_name,version=edoc.version,deleted=False).first()

        if roe:
            roe.serial_no = ''
            roe.parts = None
            roe.complete = 0
            roe.save()


def get_child_parts(
        parent_stock_code,
        parent_serial_no,
        repair_order_id,
        sort_desc=False
):
    sorting_key = "sequence"
    if sort_desc:
        sorting_key = "-" + sorting_key
    return RepairOrderPart.objects.filter(
        parent_part__stock_code=parent_stock_code,
        parent_part__serial_no=parent_serial_no,
        repair_order_id=repair_order_id,
        deleted=False
    ).order_by(sorting_key)
        
def update_repair_order_last_action_date(repair_order:Union[int,RepairOrder]):
    if not isinstance(repair_order, RepairOrder):
        repair_order = RepairOrder.objects.get(id=repair_order, deleted=False)
    
    print('repair_order.last_action_date', repair_order.last_action_date)


    if repair_order.last_action_date == timezone.now().date():
        print('Not need to update')
        return
     
    repair_order.last_action_date = timezone.now().date()
    print('Last action update to', timezone.now().date())

    repair_order.save()